From: Bartosz DziewoƄski Date: Sat, 16 Sep 2017 13:21:50 +0000 (+0200) Subject: Special:Preferences: Use OOjs UI X-Git-Tag: 1.31.0-rc.0~1519^2 X-Git-Url: http://git.cyclocoop.org/?a=commitdiff_plain;h=486e566cfef612de6773df435a74d5fc37e27174;p=lhc%2Fweb%2Fwiklou.git Special:Preferences: Use OOjs UI * Change the form to OOUI mode. Tweak some formatting to look better with this mode. Change various random links to be OOUI buttons. * Rewrite custom tabs to use OO.ui.IndexLayout instead. * Update styles and JS enhancements for OOUI widgets. * Rename ResourceLoader modules so that old skin-specific styles (from $wgResourceModuleSkinStyles) no longer apply. They tend to make no sense with the OOUI styling. Bug: T117781 Change-Id: Ie9396f0146f5020e52710c41e55ec86151ae0095 --- diff --git a/includes/Preferences.php b/includes/Preferences.php index 94854fa23d..738f8eecff 100644 --- a/includes/Preferences.php +++ b/includes/Preferences.php @@ -82,6 +82,11 @@ class Preferences { return self::$defaultPreferences; } + OutputPage::setupOOUI( + strtolower( $context->getSkin()->getSkinName() ), + $context->getLanguage()->getDir() + ); + $defaultPreferences = []; self::profilePreferences( $user, $context, $defaultPreferences ); @@ -320,14 +325,17 @@ class Preferences { 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', ]; @@ -471,16 +479,15 @@ class Preferences { $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() ] ); - - $emailAddress .= $emailAddress == '' ? $link : ( - $context->msg( 'word-separator' )->escaped() - . $context->msg( 'parentheses' )->rawParams( $link )->escaped() - ); + $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 : ( '
' . $link ); } $defaultPreferences['emailaddress'] = [ @@ -515,10 +522,10 @@ class Preferences { } else { $disableEmailPrefs = true; $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '
' . - $linkRenderer->makeKnownLink( - SpecialPage::getTitleFor( 'Confirmemail' ), - $context->msg( 'emailconfirmlink' )->text() - ) . '
'; + new OOUI\ButtonWidget( [ + 'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(), + 'label' => $context->msg( 'emailconfirmlink' )->text(), + ] ); $emailauthenticationclass = "mw-email-not-authenticated"; } } else { @@ -755,6 +762,7 @@ class Preferences { 'default' => $tzSetting, 'size' => 20, 'section' => 'rendering/timeoffset', + 'id' => 'wpTimeCorrection', ]; } @@ -997,7 +1005,7 @@ class Preferences { # # Watchlist ##################################### if ( $user->isAllowed( 'editmywatchlist' ) ) { - $editWatchlistLinks = []; + $editWatchlistLinks = ''; $editWatchlistModes = [ 'edit' => [ 'EditWatchlist', false ], 'raw' => [ 'EditWatchlist', 'raw' ], @@ -1006,16 +1014,19 @@ class Preferences { $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', ]; @@ -1138,6 +1149,12 @@ class Preferences { 'default' => $user->getTokenFromOption( 'watchlisttoken' ), 'help-message' => 'prefs-help-watchlist-token2', ]; + $defaultPreferences['watchlisttoken-info2'] = [ + 'type' => 'info', + 'section' => 'watchlist/tokenwatchlist', + 'raw' => true, + 'default' => $context->msg( 'prefs-help-watchlist-token2' )->parse(), + ]; } } @@ -1358,6 +1375,9 @@ class Preferences { $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/SpecialPreferences.php b/includes/specials/SpecialPreferences.php index 7c55e5c8cb..ed2daffa7c 100644 --- a/includes/specials/SpecialPreferences.php +++ b/includes/specials/SpecialPreferences.php @@ -50,8 +50,8 @@ class SpecialPreferences extends SpecialPage { 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 @@ class SpecialPreferences extends SpecialPage { $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 ); + + // TODO: Render fake tabs here to avoid FOUC. + // $out->addHTML( $fakeTabs ); - $out->addHTML( - Html::rawElement( 'ul', - [ - 'id' => 'preftoc', - 'role' => 'tablist' - ], - $prefTabs ) - ); $htmlForm->show(); } @@ -136,7 +118,7 @@ class SpecialPreferences extends SpecialPage { $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/forms/PreferencesForm.php b/includes/specials/forms/PreferencesForm.php index d4e5ef4fdd..28cfb8b0f0 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 @@ class PreferencesForm extends HTMLForm { * @return string */ function getButtons() { - $attrs = [ 'id' => 'mw-prefs-restoreprefs' ]; - if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) { return ''; } @@ -82,9 +78,14 @@ class PreferencesForm extends HTMLForm { 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/resources/Resources.php b/resources/Resources.php index b4a3f2f9ab..db23c333fa 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -2092,7 +2092,7 @@ return [ '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', @@ -2109,9 +2109,11 @@ return [ 'mediawiki.language', 'mediawiki.confirmCloseWindow', 'mediawiki.notification.convertmessagebox', + 'oojs-ui-widgets', + 'mediawiki.widgets.SelectWithInputWidget', ], ], - '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 7b2d71106e..596b0d6d7f 100644 --- a/resources/src/mediawiki.legacy/oldshared.css +++ b/resources/src/mediawiki.legacy/oldshared.css @@ -220,28 +220,6 @@ table.toc td { 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 45df37ffd2..fe127eb5d6 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.styles.css b/resources/src/mediawiki.special/mediawiki.special.preferences.styles.css index 33b630a948..294dcd0933 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%; -} -#preferences > fieldset table { - width: 100%; +/* 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.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,35 @@ 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; +} + +/* 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-panelLayout-framed .oo-ui-tabPanelLayout .oo-ui-panelLayout-framed { + margin-left: 0; } -.client-js #preferences > fieldset { - display: none; +.client-js #preferences .oo-ui-panelLayout-framed .oo-ui-tabPanelLayout { + padding-top: 0.5em; + padding-bottom: 0.5em; } -/* Only the 1st tab is shown by default in JS mode */ -.client-js #preferences #mw-prefsection-personal { - display: block; +.client-js #preferences > .oo-ui-panelLayout > .oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-header { + margin-bottom: 1em; } diff --git a/resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js b/resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js index dcfad271d1..9f1691c22e 100644 --- a/resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js +++ b/resources/src/mediawiki.special/mediawiki.special.preferences.tabs.js @@ -3,29 +3,10 @@ */ ( 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 // when selected. Similar to jquery.mw-jump @@ -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; - - 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; + tabs.on( 'set', updateHash ); + + /** + * @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 - // 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,23 +120,13 @@ } 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' ); if ( previousTab ) { @@ -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 03656eedea..7fbcc77eb7 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 // 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/phpunit/includes/PreferencesTest.php b/tests/phpunit/includes/PreferencesTest.php index d78c1e7dc0..b25e046d42 100644 --- a/tests/phpunit/includes/PreferencesTest.php +++ b/tests/phpunit/includes/PreferencesTest.php @@ -150,6 +150,13 @@ class PreferencesTest extends MediaWikiTestCase { /** Helper */ protected function prefsFor( $user_key ) { + // TODO This should use Preferences::getPreferences() instead of calling internal methods. + // Unfortunately that currently ignores the $user parameter if it has cached data, even for + // a different user... + OutputPage::setupOOUI( + strtolower( $this->context->getSkin()->getSkinName() ), + $this->context->getLanguage()->getDir() + ); $preferences = []; Preferences::profilePreferences( $this->prefUsers[$user_key], diff --git a/tests/selenium/pageobjects/preferences.page.js b/tests/selenium/pageobjects/preferences.page.js index 98b87fe9cb..890fe5b40a 100644 --- a/tests/selenium/pageobjects/preferences.page.js +++ b/tests/selenium/pageobjects/preferences.page.js @@ -3,8 +3,8 @@ const Page = require( './page' ); 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' );