Hello Bartosz Dziewoński, Florianschmidtwelzow, jenkins-bot, Siebrand, 
Jforrester,

I'd like you to do a code review.  Please visit

    https://gerrit.wikimedia.org/r/398071

to review the following change.


Change subject: Revert "Revert "Special:Preferences: Use OOjs UI" and 
follow-ups"
......................................................................

Revert "Revert "Special:Preferences: Use OOjs UI" and follow-ups"

This reverts commit 808e45d13d400256d36cfcd95e79a567197d9a8b.

Change-Id: I152b82bcd647d97062eb82cd2d1064609124f9bc
---
M includes/Preferences.php
M includes/specials/SpecialPasswordReset.php
M includes/specials/SpecialPreferences.php
M includes/specials/SpecialResetTokens.php
M includes/specials/forms/PreferencesForm.php
M languages/i18n/en.json
M languages/i18n/qqq.json
M resources/Resources.php
M resources/src/mediawiki.legacy/oldshared.css
M resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js
A resources/src/mediawiki.special/mediawiki.special.preferences.editfont.js
M resources/src/mediawiki.special/mediawiki.special.preferences.styles.css
M resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js
M resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js
M tests/selenium/pageobjects/preferences.page.js
15 files changed, 294 insertions(+), 215 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/71/398071/1

diff --git a/includes/Preferences.php b/includes/Preferences.php
index 2dd3e2d..924e3ad 100644
--- a/includes/Preferences.php
+++ b/includes/Preferences.php
@@ -75,6 +75,11 @@
         * @return array|null
         */
        static function getPreferences( $user, IContextSource $context ) {
+               OutputPage::setupOOUI(
+                       strtolower( $context->getSkin()->getSkinName() ),
+                       $context->getLanguage()->getDir()
+               );
+
                $defaultPreferences = [];
 
                self::profilePreferences( $user, $context, $defaultPreferences 
);
@@ -312,14 +317,17 @@
                if ( $canEditPrivateInfo && 
$authManager->allowsAuthenticationDataChange(
                        new PasswordAuthenticationRequest(), false )->isGood()
                ) {
-                       $link = $linkRenderer->makeLink( 
SpecialPage::getTitleFor( 'ChangePassword' ),
-                               $context->msg( 'prefs-resetpass' )->text(), [],
-                               [ 'returnto' => SpecialPage::getTitleFor( 
'Preferences' )->getPrefixedText() ] );
+                       $link = new OOUI\ButtonWidget( [
+                               'href' => SpecialPage::getTitleFor( 
'ChangePassword' )->getLinkURL( [
+                                       'returnto' => SpecialPage::getTitleFor( 
'Preferences' )->getPrefixedText()
+                               ] ),
+                               'label' => $context->msg( 'prefs-resetpass' 
)->text(),
+                       ] );
 
                        $defaultPreferences['password'] = [
                                'type' => 'info',
                                'raw' => true,
-                               'default' => $link,
+                               'default' => (string)$link,
                                'label-message' => 'yourpassword',
                                'section' => 'personal/info',
                        ];
@@ -463,16 +471,15 @@
 
                                $emailAddress = $user->getEmail() ? 
htmlspecialchars( $user->getEmail() ) : '';
                                if ( $canEditPrivateInfo && 
$authManager->allowsPropertyChange( 'emailaddress' ) ) {
-                                       $link = $linkRenderer->makeLink(
-                                               SpecialPage::getTitleFor( 
'ChangeEmail' ),
-                                               $context->msg( 
$user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
-                                               [],
-                                               [ 'returnto' => 
SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
+                                       $link = new OOUI\ButtonWidget( [
+                                               'href' => 
SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
+                                                       'returnto' => 
SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
+                                               ] ),
+                                               'label' =>
+                                                       $context->msg( 
$user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
+                                       ] );
 
-                                       $emailAddress .= $emailAddress == '' ? 
$link : (
-                                               $context->msg( 'word-separator' 
)->escaped()
-                                               . $context->msg( 'parentheses' 
)->rawParams( $link )->escaped()
-                                       );
+                                       $emailAddress .= $emailAddress == '' ? 
$link : ( '<br />' . $link );
                                }
 
                                $defaultPreferences['emailaddress'] = [
@@ -507,10 +514,10 @@
                                        } else {
                                                $disableEmailPrefs = true;
                                                $emailauthenticated = 
$context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
-                                                       
$linkRenderer->makeKnownLink(
-                                                               
SpecialPage::getTitleFor( 'Confirmemail' ),
-                                                               $context->msg( 
'emailconfirmlink' )->text()
-                                                       ) . '<br />';
+                                                       new OOUI\ButtonWidget( [
+                                                               'href' => 
SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
+                                                               'label' => 
$context->msg( 'emailconfirmlink' )->text(),
+                                                       ] );
                                                $emailauthenticationclass = 
"mw-email-not-authenticated";
                                        }
                                } else {
@@ -747,6 +754,7 @@
                        'default' => $tzSetting,
                        'size' => 20,
                        'section' => 'rendering/timeoffset',
+                       'id' => 'wpTimeCorrection',
                ];
        }
 
@@ -989,7 +997,7 @@
 
                # # Watchlist #####################################
                if ( $user->isAllowed( 'editmywatchlist' ) ) {
-                       $editWatchlistLinks = [];
+                       $editWatchlistLinks = '';
                        $editWatchlistModes = [
                                'edit' => [ 'EditWatchlist', false ],
                                'raw' => [ 'EditWatchlist', 'raw' ],
@@ -998,16 +1006,19 @@
                        $linkRenderer = 
MediaWikiServices::getInstance()->getLinkRenderer();
                        foreach ( $editWatchlistModes as $editWatchlistMode => 
$mode ) {
                                // Messages: prefs-editwatchlist-edit, 
prefs-editwatchlist-raw, prefs-editwatchlist-clear
-                               $editWatchlistLinks[] = 
$linkRenderer->makeKnownLink(
-                                       SpecialPage::getTitleFor( $mode[0], 
$mode[1] ),
-                                       new HtmlArmor( $context->msg( 
"prefs-editwatchlist-{$editWatchlistMode}" )->parse() )
-                               );
+                               $editWatchlistLinks .=
+                                       new OOUI\ButtonWidget( [
+                                               'href' => 
SpecialPage::getTitleFor( $mode[0], $mode[1] )->getLinkURL(),
+                                               'label' => new OOUI\HtmlSnippet(
+                                                       $context->msg( 
"prefs-editwatchlist-{$editWatchlistMode}" )->parse()
+                                               ),
+                                       ] );
                        }
 
                        $defaultPreferences['editwatchlist'] = [
                                'type' => 'info',
                                'raw' => true,
-                               'default' => $context->getLanguage()->pipeList( 
$editWatchlistLinks ),
+                               'default' => $editWatchlistLinks,
                                'label-message' => 'prefs-editwatchlist-label',
                                'section' => 'watchlist/editwatchlist',
                        ];
@@ -1123,12 +1134,20 @@
                        $defaultPreferences['watchlisttoken'] = [
                                'type' => 'api',
                        ];
+
+                       $tokenButton = new OOUI\ButtonWidget( [
+                               'href' => SpecialPage::getTitleFor( 
'ResetTokens' )->getLinkURL( [
+                                       'returnto' => SpecialPage::getTitleFor( 
'Preferences' )->getPrefixedText()
+                               ] ),
+                               'label' => $context->msg( 
'prefs-watchlist-managetokens' )->text(),
+                       ] );
                        $defaultPreferences['watchlisttoken-info'] = [
                                'type' => 'info',
                                'section' => 'watchlist/tokenwatchlist',
                                'label-message' => 'prefs-watchlist-token',
-                               'default' => $user->getTokenFromOption( 
'watchlisttoken' ),
-                               'help-message' => 'prefs-help-watchlist-token2',
+                               'help-message' => 'prefs-help-tokenmanagement',
+                               'raw' => true,
+                               'default' => (string)$tokenButton,
                        ];
                }
        }
@@ -1350,6 +1369,9 @@
                $formClass = 'PreferencesForm',
                array $remove = []
        ) {
+               // We use ButtonWidgets in some of the getPreferences() 
functions
+               $context->getOutput()->enableOOUI();
+
                $formDescriptor = self::getPreferences( $user, $context );
                if ( count( $remove ) ) {
                        $removeKeys = array_flip( $remove );
diff --git a/includes/specials/SpecialPasswordReset.php 
b/includes/specials/SpecialPasswordReset.php
index a4f16bd..bf8dea6 100644
--- a/includes/specials/SpecialPasswordReset.php
+++ b/includes/specials/SpecialPasswordReset.php
@@ -110,6 +110,8 @@
        public function alterForm( HTMLForm $form ) {
                $resetRoutes = $this->getConfig()->get( 'PasswordResetRoutes' );
 
+               $form->setSubmitDestructive();
+
                $form->addHiddenFields( $this->getRequest()->getValues( 
'returnto', 'returntoquery' ) );
 
                $i = 0;
diff --git a/includes/specials/SpecialPreferences.php 
b/includes/specials/SpecialPreferences.php
index 8ad1630..7fa74af 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -50,8 +50,8 @@
                        return;
                }
 
-               $out->addModules( 'mediawiki.special.preferences' );
-               $out->addModuleStyles( 'mediawiki.special.preferences.styles' );
+               $out->addModules( 'mediawiki.special.preferences.ooui' );
+               $out->addModuleStyles( 
'mediawiki.special.preferences.styles.ooui' );
 
                $session = $this->getRequest()->getSession();
                if ( $session->get( 'specialPreferencesSaveSuccess' ) ) {
@@ -83,37 +83,19 @@
 
                $htmlForm = $this->getFormObject( $user, $this->getContext() );
                $htmlForm->setSubmitCallback( [ 'Preferences', 'tryUISubmit' ] 
);
-               $sectionTitles = $htmlForm->getPreferenceSections();
 
-               $prefTabs = '';
-               foreach ( $sectionTitles as $key ) {
-                       $prefTabs .= Html::rawElement( 'li',
-                               [
-                                       'role' => 'presentation',
-                                       'class' => ( $key === 'personal' ) ? 
'selected' : null
-                               ],
-                               Html::rawElement( 'a',
-                                       [
-                                               'id' => 'preftab-' . $key,
-                                               'role' => 'tab',
-                                               'href' => '#mw-prefsection-' . 
$key,
-                                               'aria-controls' => 
'mw-prefsection-' . $key,
-                                               'aria-selected' => ( $key === 
'personal' ) ? 'true' : 'false',
-                                               'tabIndex' => ( $key === 
'personal' ) ? 0 : -1,
-                                       ],
-                                       $htmlForm->getLegend( $key )
-                               )
-                       );
+               $prefTabs = [];
+               foreach ( $htmlForm->getPreferenceSections() as $key ) {
+                       $prefTabs[] = [
+                               'name' => $key,
+                               'label' => $htmlForm->getLegend( $key ),
+                       ];
                }
+               $out->addJsConfigVars( 'wgPreferencesTabs', $prefTabs );
 
-               $out->addHTML(
-                       Html::rawElement( 'ul',
-                               [
-                                       'id' => 'preftoc',
-                                       'role' => 'tablist'
-                               ],
-                               $prefTabs )
-               );
+               // TODO: Render fake tabs here to avoid FOUC.
+               // $out->addHTML( $fakeTabs );
+
                $htmlForm->show();
        }
 
@@ -136,7 +118,7 @@
 
                $context = new DerivativeContext( $this->getContext() );
                $context->setTitle( $this->getPageTitle( 'reset' ) ); // Reset 
subpage
-               $htmlForm = new HTMLForm( [], $context, 'prefs-restore' );
+               $htmlForm = HTMLForm::factory( 'ooui', [], $context, 
'prefs-restore' );
 
                $htmlForm->setSubmitTextMsg( 'restoreprefs' );
                $htmlForm->setSubmitDestructive();
diff --git a/includes/specials/SpecialResetTokens.php 
b/includes/specials/SpecialResetTokens.php
index 964a261..d5b0903 100644
--- a/includes/specials/SpecialResetTokens.php
+++ b/includes/specials/SpecialResetTokens.php
@@ -121,6 +121,7 @@
         * @param HTMLForm $form
         */
        protected function alterForm( HTMLForm $form ) {
+               $form->setSubmitDestructive();
                if ( $this->getTokensList() ) {
                        $form->setSubmitTextMsg( 'resettokens-resetbutton' );
                } else {
diff --git a/includes/specials/forms/PreferencesForm.php 
b/includes/specials/forms/PreferencesForm.php
index d4e5ef4..28cfb8b 100644
--- a/includes/specials/forms/PreferencesForm.php
+++ b/includes/specials/forms/PreferencesForm.php
@@ -18,12 +18,10 @@
  * @file
  */
 
-use MediaWiki\MediaWikiServices;
-
 /**
  * Form to edit user preferences.
  */
-class PreferencesForm extends HTMLForm {
+class PreferencesForm extends OOUIHTMLForm {
        // Override default value from HTMLForm
        protected $mSubSectionBeforeFields = false;
 
@@ -71,8 +69,6 @@
         * @return string
         */
        function getButtons() {
-               $attrs = [ 'id' => 'mw-prefs-restoreprefs' ];
-
                if ( !$this->getModifiedUser()->isAllowedAny( 
'editmyprivateinfo', 'editmyoptions' ) ) {
                        return '';
                }
@@ -82,9 +78,14 @@
                if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) {
                        $t = $this->getTitle()->getSubpage( 'reset' );
 
-                       $linkRenderer = 
MediaWikiServices::getInstance()->getLinkRenderer();
-                       $html .= "\n" . $linkRenderer->makeLink( $t, 
$this->msg( 'restoreprefs' )->text(),
-                               Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' 
] ) );
+                       $html .= new OOUI\ButtonWidget( [
+                               'infusable' => true,
+                               'id' => 'mw-prefs-restoreprefs',
+                               'label' => $this->msg( 'restoreprefs' )->text(),
+                               'href' => $t->getLinkURL(),
+                               'flags' => [ 'destructive' ],
+                               'framed' => false,
+                       ] );
 
                        $html = Xml::tags( 'div', [ 'class' => 
'mw-prefs-buttons' ], $html );
                }
diff --git a/languages/i18n/en.json b/languages/i18n/en.json
index 87bd13e..5c25de6 100644
--- a/languages/i18n/en.json
+++ b/languages/i18n/en.json
@@ -1027,6 +1027,7 @@
        "prefs-watchlist-edits": "Maximum number of changes to show in 
watchlist:",
        "prefs-watchlist-edits-max": "Maximum number: 1000",
        "prefs-watchlist-token": "Watchlist token:",
+       "prefs-watchlist-managetokens": "Manage tokens",
        "prefs-misc": "Misc",
        "prefs-resetpass": "Change password",
        "prefs-changeemail": "Change or remove email address",
@@ -1044,7 +1045,7 @@
        "recentchangesdays-max": "Maximum $1 {{PLURAL:$1|day|days}}",
        "recentchangescount": "Number of edits to show by default:",
        "prefs-help-recentchangescount": "This includes recent changes, page 
histories, and logs.",
-       "prefs-help-watchlist-token2": "This is the secret key to the web feed 
of your watchlist.\nAnyone who knows it will be able to read your watchlist, so 
do not share it.\nIf you need to, [[Special:ResetTokens|you can reset it]].",
+       "prefs-help-tokenmanagement": "You can see and reset the secret key for 
your account that can access the Web feed of your watchlist. Anyone who knows 
the key will be able to read your watchlist, so do not share it.",
        "savedprefs": "Your preferences have been saved.",
        "savedrights": "The user groups of {{GENDER:$1|$1}} have been saved.",
        "timezonelegend": "Time zone:",
diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json
index 1988a46..27d8e43 100644
--- a/languages/i18n/qqq.json
+++ b/languages/i18n/qqq.json
@@ -1222,6 +1222,7 @@
        "prefs-watchlist-edits": "Used in [[Special:Preferences]], tab 
\"Watchlist\".",
        "prefs-watchlist-edits-max": "Shown as hint in [[Special:Preferences]], 
tab \"Watchlist\"",
        "prefs-watchlist-token": "Used in [[Special:Preferences]], tab 
Watchlist.",
+       "prefs-watchlist-managetokens": "Label for the button to see and reset 
the user's private tokens",
        "prefs-misc": "Tab used on the [[Special:Preferences|user preferences]] 
special page.",
        "prefs-resetpass": "Button on user data tab in user preferences. When 
you click the button you go to the special page 
[[Special:ResetPass]].\n\n{{Identical|Change password}}",
        "prefs-changeemail": "Link on [[Special:Preferences]] to 
[[Special:ChangeEmail]]. [[Special:ChangeEmail]] also allows removing email 
address. \n\nSee also:\n* {{msg-mw|prefs-help-email-required|help}}\n* 
{{msg-mw|prefs-help-email|help}}\n* {{msg-mw|prefs-help-email-others|help}}\n* 
{{msg-mw|prefs-setemail|link title}}",
@@ -1239,7 +1240,7 @@
        "recentchangesdays-max": "Shown as hint in [[Special:Preferences]], tab 
\"Recent changes\". Parameters:\n* $1 - number of days\nSee also:\n* 
{{msg-mw|Prefs-watchlist-days-max}}",
        "recentchangescount": "Used in [[Special:Preferences]], tab \"Recent 
changes\".",
        "prefs-help-recentchangescount": "Used in [[Special:Preferences]], tab 
\"Recent changes\".",
-       "prefs-help-watchlist-token2": "Used in [[Special:Preferences]], tab 
Watchlist. (Formerly in {{msg-mw|prefs-help-watchlist-token}}.)",
+       "prefs-help-tokenmanagement": "Used in [[Special:Preferences]], 
Watchlist tab.",
        "savedprefs": "This message appears after saving changes to your user 
preferences.",
        "savedrights": "This message appears after saving the user groups on 
[[Special:UserRights]].\n* $1 - The user name of the user which groups was 
saved.",
        "timezonelegend": "{{Identical|Time zone}}",
diff --git a/resources/Resources.php b/resources/Resources.php
index 0e6939b..0665a2a 100644
--- a/resources/Resources.php
+++ b/resources/Resources.php
@@ -2098,10 +2098,11 @@
        'mediawiki.special.pagesWithProp' => [
                'styles' => 
'resources/src/mediawiki.special/mediawiki.special.pagesWithProp.css',
        ],
-       'mediawiki.special.preferences' => [
+       'mediawiki.special.preferences.ooui' => [
                'scripts' => [
                        
'resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js',
                        
'resources/src/mediawiki.special/mediawiki.special.preferences.convertmessagebox.js',
+                       
'resources/src/mediawiki.special/mediawiki.special.preferences.editfont.js',
                        
'resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js',
                        
'resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js',
                ],
@@ -2115,9 +2116,12 @@
                        'mediawiki.language',
                        'mediawiki.confirmCloseWindow',
                        'mediawiki.notification.convertmessagebox',
+                       'oojs-ui-widgets',
+                       'mediawiki.widgets.SelectWithInputWidget',
+                       'mediawiki.editfont.styles',
                ],
        ],
-       'mediawiki.special.preferences.styles' => [
+       'mediawiki.special.preferences.styles.ooui' => [
                'styles' => 
'resources/src/mediawiki.special/mediawiki.special.preferences.styles.css',
        ],
        'mediawiki.special.recentchanges' => [
diff --git a/resources/src/mediawiki.legacy/oldshared.css 
b/resources/src/mediawiki.legacy/oldshared.css
index 7b2d711..596b0d6 100644
--- a/resources/src/mediawiki.legacy/oldshared.css
+++ b/resources/src/mediawiki.legacy/oldshared.css
@@ -220,28 +220,6 @@
        font-size: larger;
 }
 
-/* preference page with js-genrated toc */
-#preftoc {
-       float: left;
-       margin: 1em 1em 1em 1em;
-       width: 13em;
-}
-
-#preftoc li {
-       border: 1px solid #fff;
-}
-
-#preftoc li.selected {
-       background-color: #f9f9f9;
-       border: 1px dashed #aaa;
-}
-
-#preftoc a,
-#preftoc a:active {
-       display: block;
-       color: #005189;
-}
-
 .mw-prefs-buttons {
        clear: left;
        float: left;
diff --git 
a/resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js 
b/resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js
index 45df37f..fe127eb 100644
--- 
a/resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js
+++ 
b/resources/src/mediawiki.special/mediawiki.special.preferences.confirmClose.js
@@ -4,9 +4,11 @@
  */
 ( function ( mw, $ ) {
        $( function () {
-               var allowCloseWindow;
+               var allowCloseWindow, saveButton, restoreButton;
 
-               // Check if all of the form values are unchanged
+               // Check if all of the form values are unchanged.
+               // (This function could be changed to infuse and check OOUI 
widgets, but that would only make it
+               // slower and more complicated. It works fine to treat them as 
HTML elements.)
                function isPrefsChanged() {
                        var inputs = $( '#mw-prefs-form :input[name]' ),
                                input, $input, inputType,
@@ -41,12 +43,15 @@
                        return false;
                }
 
+               saveButton = OO.ui.infuse( $( '#prefcontrol' ) );
+               restoreButton = OO.ui.infuse( $( '#mw-prefs-restoreprefs' ) );
+
                // Disable the button to save preferences unless preferences 
have changed
                // Check if preferences have been changed before JS has 
finished loading
                if ( !isPrefsChanged() ) {
-                       $( '#prefcontrol' ).prop( 'disabled', true );
-                       $( '#preferences > fieldset' ).one( 'change keydown 
mousedown', function () {
-                               $( '#prefcontrol' ).prop( 'disabled', false );
+                       saveButton.setDisabled( true );
+                       $( '#preferences .oo-ui-fieldsetLayout' ).one( 'change 
keydown mousedown', function () {
+                               saveButton.setDisabled( false );
                        } );
                }
 
@@ -58,6 +63,11 @@
                        namespace: 'prefswarning'
                } );
                $( '#mw-prefs-form' ).submit( $.proxy( allowCloseWindow, 
'release' ) );
-               $( '#mw-prefs-restoreprefs' ).click( $.proxy( allowCloseWindow, 
'release' ) );
+               restoreButton.on( 'click', function () {
+                       allowCloseWindow.release();
+                       // The default behavior of events in OOUI is always 
prevented. Follow the link manually.
+                       // Note that middle-click etc. still works, as it 
doesn't emit a OOUI 'click' event.
+                       location.href = restoreButton.getHref();
+               } );
        } );
 }( mediaWiki, jQuery ) );
diff --git 
a/resources/src/mediawiki.special/mediawiki.special.preferences.editfont.js 
b/resources/src/mediawiki.special/mediawiki.special.preferences.editfont.js
new file mode 100644
index 0000000..fe48886
--- /dev/null
+++ b/resources/src/mediawiki.special/mediawiki.special.preferences.editfont.js
@@ -0,0 +1,32 @@
+/*!
+ * JavaScript for Special:Preferences: editfont field enhancements.
+ */
+( function ( mw, $ ) {
+       $( function () {
+               var widget, lastValue;
+
+               try {
+                       widget = OO.ui.infuse( $( '#mw-input-wpeditfont' ) );
+               } catch ( err ) {
+                       // This preference could theoretically be disabled 
($wgHiddenPrefs)
+                       return;
+               }
+
+               // Style options
+               widget.dropdownWidget.menu.items.forEach( function ( item ) {
+                       item.$label.addClass( 'mw-editfont-' + item.getData() );
+               } );
+
+               function updateLabel( value ) {
+                       // Style selected item label
+                       widget.dropdownWidget.$label
+                               .removeClass( 'mw-editfont-' + lastValue )
+                               .addClass( 'mw-editfont-' + value );
+                       lastValue = value;
+               }
+
+               widget.on( 'change', updateLabel );
+               updateLabel( widget.getValue() );
+
+       } );
+}( mediaWiki, jQuery ) );
diff --git 
a/resources/src/mediawiki.special/mediawiki.special.preferences.styles.css 
b/resources/src/mediawiki.special/mediawiki.special.preferences.styles.css
index 33b630a..0ee4ac2 100644
--- a/resources/src/mediawiki.special/mediawiki.special.preferences.styles.css
+++ b/resources/src/mediawiki.special/mediawiki.special.preferences.styles.css
@@ -1,27 +1,28 @@
 /* Reuses colors from mediawiki.legacy/shared.css */
-.mw-email-not-authenticated .mw-input,
-.mw-email-none .mw-input {
+.mw-email-not-authenticated .oo-ui-labelWidget,
+.mw-email-none .oo-ui-labelWidget {
        border: 1px solid #fde29b;
        background-color: #fdf1d1;
        color: #000;
 }
 /* Authenticated email field has its own class too. Unstyled by default */
 /*
-.mw-email-authenticated .mw-input { }
+.mw-email-authenticated .oo-ui-labelWidget { }
 */
-/* This breaks due to nolabel styling */
-#preferences > fieldset td.mw-label {
-       width: 20%;
+
+/* This is needed because add extra buttons in a weird way */
+.mw-prefs-buttons .mw-htmlform-submit-buttons {
+       margin: 0;
+       display: inline;
 }
 
-#preferences > fieldset table {
-       width: 100%;
-}
-#preferences > fieldset table.mw-htmlform-matrix {
-       width: auto;
+.mw-prefs-buttons {
+       margin-top: 1em;
 }
 
-/* The CSS below is also for JS enabled version, because we want to prevent 
FOUC */
+#prefcontrol {
+       margin-right: 0.5em;
+}
 
 /*
  * Hide, but keep accessible for screen-readers.
@@ -33,15 +34,74 @@
        zoom: 1;
 }
 
-.client-nojs #preftoc {
-       display: none;
+/* Override OOUI styles so that dropdowns near the bottom of the form don't 
get clipped,
+ * e.g.'Appearance' / 'Threshold for stub link formatting'. This is hacky and 
bad, it would be
+ * better solved by setting overlays for the widgets, but we can't do it from 
PHP... */
+#preferences .oo-ui-panelLayout {
+       position: static;
+       overflow: visible;
+       -webkit-transform: none;
+       transform: none;
 }
 
-.client-js #preferences > fieldset {
-       display: none;
+#preferences .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
+       border-color: #c8ccd1;
+       border-width: 1px 0 0;
+       border-radius: 0;
+       padding-left: 0;
+       padding-right: 0;
+       box-shadow: none;
 }
 
-/* Only the 1st tab is shown by default in JS mode */
-.client-js #preferences #mw-prefsection-personal {
+/* Tweak the margins to reduce the shifting of form contents
+ * after JS code loads and rearranges the page */
+.client-js #preferences > .oo-ui-panelLayout {
+       margin: 1em 0;
+}
+
+.client-js #preferences .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
+       margin-left: 0.25em;
+}
+
+.client-js #preferences .oo-ui-tabPanelLayout {
+       padding-top: 0.5em;
+       padding-bottom: 0.5em;
+}
+
+.client-js #preferences .oo-ui-tabPanelLayout .oo-ui-panelLayout-framed {
+       margin-left: 0;
+       margin-bottom: 0;
+       border: 0;
+       padding-top: 0;
+}
+
+.client-js #preferences > .oo-ui-panelLayout > .oo-ui-fieldsetLayout > 
.oo-ui-fieldsetLayout-header {
+       margin-bottom: 1em;
+}
+
+/* Make the "Basic information" section more compact */
+/* OOUI's `align: 'left'` for FieldLayouts sucks, so we do our own */
+#mw-htmlform-info > .oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > 
.oo-ui-fieldLayout-header {
+       width: 20%;
+       display: inline-block;
+       vertical-align: middle;
+       padding: 0;
+}
+
+#mw-htmlform-info > .oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > 
.oo-ui-fieldLayout-field {
+       width: 80%;
+       display: inline-block;
+       vertical-align: middle;
+}
+
+/* Expand the dropdown and textfield of "Time zone" field to the */
+/* usual maximum width and display them on separate lines. */
+#wpTimeCorrection .oo-ui-dropdownInputWidget,
+#wpTimeCorrection .oo-ui-textInputWidget {
        display: block;
+       max-width: 50em;
+}
+
+#wpTimeCorrection .oo-ui-textInputWidget {
+       margin-top: 0.5em;
 }
diff --git 
a/resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js 
b/resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js
index dcfad27..9f1691c 100644
--- a/resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js
+++ b/resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js
@@ -3,28 +3,9 @@
  */
 ( function ( mw, $ ) {
        $( function () {
-               var $preftoc, $preferences, $fieldsets, labelFunc, previousTab;
+               var $preferences, tabs, wrapper, previousTab;
 
-               labelFunc = function () {
-                       return this.id.replace( /^mw-prefsection/g, 'preftab' );
-               };
-
-               $preftoc = $( '#preftoc' );
                $preferences = $( '#preferences' );
-
-               $fieldsets = $preferences.children( 'fieldset' )
-                       .attr( {
-                               role: 'tabpanel',
-                               'aria-labelledby': labelFunc
-                       } );
-               $fieldsets.not( '#mw-prefsection-personal' )
-                       .hide()
-                       .attr( 'aria-hidden', 'true' );
-
-               // T115692: The following is kept for backwards compatibility 
with older skins
-               $preferences.addClass( 'jsprefs' );
-               $fieldsets.addClass( 'prefsection' );
-               $fieldsets.children( 'legend' ).addClass( 'mainLegend' );
 
                // Make sure the accessibility tip is selectable so that screen 
reader users take notice,
                // but hide it per default to reduce interface clutter. Also 
make sure it becomes visible
@@ -38,62 +19,76 @@
                                } else {
                                        $( this ).css( 'height', 'auto' );
                                }
-                       } ).insertBefore( $preftoc );
+                       } ).prependTo( '#mw-content-text' );
 
-               /**
-                * It uses document.getElementById for security reasons (HTML 
injections in $()).
-                *
-                * @ignore
-                * @param {string} name the name of a tab without the prefix 
("mw-prefsection-")
-                * @param {string} [mode] A hash will be set according to the 
current
-                *  open section. Set mode 'noHash' to surpress this.
-                */
-               function switchPrefTab( name, mode ) {
-                       var $tab, scrollTop;
+               tabs = new OO.ui.IndexLayout( {
+                       expanded: false,
+                       // Do not remove focus from the tabs menu after 
choosing a tab
+                       autoFocus: false
+               } );
+
+               mw.config.get( 'wgPreferencesTabs' ).forEach( function ( 
tabConfig ) {
+                       var panel, $panelContents;
+
+                       panel = new OO.ui.TabPanelLayout( tabConfig.name, {
+                               expanded: false,
+                               label: tabConfig.label
+                       } );
+                       $panelContents = $( '#mw-prefsection-' + tabConfig.name 
);
+
+                       // Hide the unnecessary PHP PanelLayouts
+                       // (Do not use .remove(), as that would remove event 
handlers for everything inside them)
+                       $panelContents.parent().detach();
+
+                       panel.$element.append( $panelContents );
+                       tabs.addTabPanels( [ panel ] );
+
+                       // Remove duplicate labels
+                       // (This must be after .addTabPanels(), otherwise the 
tab item doesn't exist yet)
+                       $panelContents.children( 'legend' ).remove();
+                       $panelContents.attr( 'aria-labelledby', 
panel.getTabItem().getElementId() );
+               } );
+
+               wrapper = new OO.ui.PanelLayout( {
+                       expanded: false,
+                       padded: false,
+                       framed: true
+               } );
+               wrapper.$element.append( tabs.$element );
+               $preferences.prepend( wrapper.$element );
+
+               function updateHash( panel ) {
+                       var scrollTop, active;
                        // Handle hash manually to prevent jumping,
                        // therefore save and restore scrollTop to prevent 
jumping.
                        scrollTop = $( window ).scrollTop();
-                       if ( mode !== 'noHash' ) {
-                               location.hash = '#mw-prefsection-' + name;
+                       // Changing the hash apparently causes keyboard focus 
to be lost?
+                       // Save and restore it. This makes no sense though.
+                       active = document.activeElement;
+                       location.hash = '#mw-prefsection-' + panel.getName();
+                       if ( active ) {
+                               active.focus();
                        }
                        $( window ).scrollTop( scrollTop );
-
-                       $preftoc.find( 'li' ).removeClass( 'selected' )
-                               .find( 'a' ).attr( {
-                                       tabIndex: -1,
-                                       'aria-selected': 'false'
-                               } );
-
-                       $tab = $( document.getElementById( 'preftab-' + name ) 
);
-                       if ( $tab.length ) {
-                               $tab.attr( {
-                                       tabIndex: 0,
-                                       'aria-selected': 'true'
-                               } ).focus()
-                                       .parent().addClass( 'selected' );
-
-                               $preferences.children( 'fieldset' 
).hide().attr( 'aria-hidden', 'true' );
-                               $( document.getElementById( 'mw-prefsection-' + 
name ) ).show().attr( 'aria-hidden', 'false' );
-                       }
                }
 
-               // Enable keyboard users to use left and right keys to switch 
tabs
-               $preftoc.on( 'keydown', function ( event ) {
-                       var keyLeft = 37,
-                               keyRight = 39,
-                               $el;
+               tabs.on( 'set', updateHash );
 
-                       if ( event.keyCode === keyLeft ) {
-                               $el = $( '#preftoc li.selected' ).prev().find( 
'a' );
-                       } else if ( event.keyCode === keyRight ) {
-                               $el = $( '#preftoc li.selected' ).next().find( 
'a' );
-                       } else {
-                               return;
+               /**
+                * @ignore
+                * @param {string} name the name of a tab without the prefix 
("mw-prefsection-")
+                * @param {string} [mode] A hash will be set according to the 
current
+                *  open section. Set mode 'noHash' to supress this.
+                */
+               function switchPrefTab( name, mode ) {
+                       if ( mode === 'noHash' ) {
+                               tabs.off( 'set', updateHash );
                        }
-                       if ( $el.length > 0 ) {
-                               switchPrefTab( $el.attr( 'href' ).replace( 
'#mw-prefsection-', '' ) );
+                       tabs.setTabPanel( name );
+                       if ( mode === 'noHash' ) {
+                               tabs.on( 'set', updateHash );
                        }
-               } );
+               }
 
                // Jump to correct section as indicated by the hash.
                // This function is called onload and onhashchange.
@@ -115,15 +110,9 @@
                        }
                }
 
-               // In browsers that support the onhashchange event we will not 
bind click
-               // handlers and instead let the browser do the default behavior 
(clicking the
-               // <a href="#.."> will naturally set the hash, handled by 
onhashchange.
-               // But other things that change the hash will also be caught 
(e.g. using
+               // Handle other things that change the hash (e.g. using
                // the Back and Forward browser navigation).
-               // Note the special check for IE "compatibility" mode.
-               if ( 'onhashchange' in window &&
-                       ( document.documentMode === undefined || 
document.documentMode >= 8 )
-               ) {
+               if ( 'onhashchange' in window ) {
                        $( window ).on( 'hashchange', function () {
                                var hash = location.hash;
                                if ( hash.match( /^#mw-[\w-]+/ ) ) {
@@ -131,22 +120,12 @@
                                } else if ( hash === '' ) {
                                        switchPrefTab( 'personal', 'noHash' );
                                }
-                       } )
-                               // Run the function immediately to select the 
proper tab on startup.
-                               .trigger( 'hashchange' );
-               // In older browsers we'll bind a click handler as fallback.
-               // We must not have onhashchange *and* the click handlers, 
otherwise
-               // the click handler calls switchPrefTab() which sets the hash 
value,
-               // which triggers onhashchange and calls switchPrefTab() again.
-               } else {
-                       $preftoc.on( 'click', 'li a', function ( e ) {
-                               switchPrefTab( $( this ).attr( 'href' 
).replace( '#mw-prefsection-', '' ) );
-                               e.preventDefault();
                        } );
-                       // If we've reloaded the page or followed an 
open-in-new-window,
-                       // make the selected tab visible.
-                       detectHash();
                }
+
+               // If we've reloaded the page or followed an open-in-new-window,
+               // make the selected tab visible.
+               detectHash();
 
                // Restore the active tab after saving the preferences
                previousTab = mw.storage.session.get( 'mwpreferences-prevTab' );
@@ -157,7 +136,7 @@
                }
 
                $( '#mw-prefs-form' ).on( 'submit', function () {
-                       var value = $( $preftoc ).find( 'li.selected a' ).attr( 
'id' ).replace( 'preftab-', '' );
+                       var value = tabs.getCurrentTabPanelName();
                        mw.storage.session.set( 'mwpreferences-prevTab', value 
);
                } );
 
diff --git 
a/resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js 
b/resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js
index 03656ee..7fbcc77 100644
--- a/resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js
+++ b/resources/src/mediawiki.special/mediawiki.special.preferences.timezone.js
@@ -4,13 +4,19 @@
 ( function ( mw, $ ) {
        $( function () {
                var
-                       $tzSelect, $tzTextbox, $localtimeHolder, servertime;
+                       timezoneWidget, $localtimeHolder, servertime;
 
                // Timezone functions.
                // Guesses Timezone from browser and updates fields onchange.
 
-               $tzSelect = $( '#mw-input-wptimecorrection' );
-               $tzTextbox = $( '#mw-input-wptimecorrection-other' );
+               // This is identical to OO.ui.infuse( ... ), but it makes the 
class name of the result known.
+               try {
+                       timezoneWidget = 
mw.widgets.SelectWithInputWidget.static.infuse( $( '#wpTimeCorrection' ) );
+               } catch ( err ) {
+                       // This preference could theoretically be disabled 
($wgHiddenPrefs)
+                       timezoneWidget = null;
+               }
+
                $localtimeHolder = $( '#wpLocalTime' );
                servertime = parseInt( $( 'input[name="wpServerTime"]' ).val(), 
10 );
 
@@ -48,21 +54,21 @@
 
                function updateTimezoneSelection() {
                        var minuteDiff, localTime,
-                               type = $tzSelect.val();
+                               type = timezoneWidget.dropdowninput.getValue();
 
                        if ( type === 'other' ) {
                                // User specified time zone manually in <input>
                                // Grab data from the textbox, parse it.
-                               minuteDiff = hoursToMinutes( $tzTextbox.val() );
+                               minuteDiff = hoursToMinutes( 
timezoneWidget.textinput.getValue() );
                        } else {
                                // Time zone not manually specified by user
                                if ( type === 'guess' ) {
                                        // Get browser timezone & fill it in
                                        minuteDiff = -( new 
Date().getTimezoneOffset() );
-                                       $tzTextbox.val( minutesToHours( 
minuteDiff ) );
-                                       $tzSelect.val( 'other' );
+                                       timezoneWidget.textinput.setValue( 
minutesToHours( minuteDiff ) );
+                                       timezoneWidget.dropdowninput.setValue( 
'other' );
                                } else {
-                                       // Grab data from the $tzSelect value
+                                       // Grab data from the dropdown value
                                        minuteDiff = parseInt( type.split( '|' 
)[ 1 ], 10 ) || 0;
                                }
                        }
@@ -76,9 +82,9 @@
                        $localtimeHolder.text( mw.language.convertNumber( 
minutesToHours( localTime ) ) );
                }
 
-               if ( $tzSelect.length && $tzTextbox.length ) {
-                       $tzSelect.change( updateTimezoneSelection );
-                       $tzTextbox.blur( updateTimezoneSelection );
+               if ( timezoneWidget ) {
+                       timezoneWidget.dropdowninput.on( 'change', 
updateTimezoneSelection );
+                       timezoneWidget.textinput.on( 'change', 
updateTimezoneSelection );
                        updateTimezoneSelection();
                }
 
diff --git a/tests/selenium/pageobjects/preferences.page.js 
b/tests/selenium/pageobjects/preferences.page.js
index 98b87fe..890fe5b 100644
--- a/tests/selenium/pageobjects/preferences.page.js
+++ b/tests/selenium/pageobjects/preferences.page.js
@@ -3,8 +3,8 @@
 
 class PreferencesPage extends Page {
 
-       get realName() { return browser.element( '#mw-input-wprealname' ); }
-       get save() { return browser.element( '#prefcontrol' ); }
+       get realName() { return browser.element( '#mw-input-wprealname 
.oo-ui-inputWidget-input' ); }
+       get save() { return browser.element( '#prefcontrol 
.oo-ui-buttonElement-button' ); }
 
        open() {
                super.open( 'Special:Preferences' );

-- 
To view, visit https://gerrit.wikimedia.org/r/398071
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I152b82bcd647d97062eb82cd2d1064609124f9bc
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Esanders <esand...@wikimedia.org>
Gerrit-Reviewer: Bartosz Dziewoński <matma....@gmail.com>
Gerrit-Reviewer: Florianschmidtwelzow <florian.schmidt.stargatewis...@gmail.com>
Gerrit-Reviewer: Jforrester <jforres...@wikimedia.org>
Gerrit-Reviewer: Siebrand <siebr...@kitano.nl>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to